package com.taxbyb.open;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.taxbyb.open.dto.RequestDTO;
import com.taxbyb.open.dto.ResponseDTO;
import com.taxbyb.open.util.SignUtil;
import org.nutz.http.Header;
import org.nutz.http.Http;
import org.nutz.http.Response;
import org.nutz.json.Json;
import org.nutz.lang.Strings;
import org.nutz.lang.util.NutMap;

public class MeterOpenClient {
    private final String baseUrl;
    private final String appId;
    private final Sign signCipher;

    /**
     * 初始化客户端
     *
     * @param baseUrl         接口地址
     * @param appId           应用id
     * @param appPrivateKey   应用私钥
     * @param serverPublicKey 平台公钥
     */
    public MeterOpenClient(String baseUrl, String appId, String appPrivateKey, String serverPublicKey) {
        this.baseUrl = baseUrl;
        this.appId = appId;

        // 使用应用私钥和平台公钥来计算签名
        signCipher = new Sign(SignAlgorithm.SHA256withRSA, appPrivateKey, serverPublicKey);
    }

    /**
     * 查询表具信息
     *
     * @param meterAddr 表地址
     * @param houseNum  户号
     * @param customer  客户姓名
     * @return 表数据
     */
    public NutMap queryMeter(String meterAddr, Integer houseNum, String customer) {
        // 请求参数
        NutMap data = NutMap.NEW()
                .setv("meterAddr", meterAddr)
                .setv("houseNum", houseNum)
                .setv("customer", customer);

        RequestDTO req = buildRequestDTO();
        req.setMethod("meter.info.query");
        req.setData(Base64.encode(Json.toJson(data)));

        // 提交请求
        ResponseDTO rsp = doRequest(req);
        return getResponseDataMap(rsp);
    }

    public NutMap hubRecharge(String meterAddr, double money) {
        // 请求参数
        NutMap data = NutMap.NEW()
                .setv("meterAddr", meterAddr)
                .setv("money", String.valueOf(money));

        RequestDTO req = buildRequestDTO();
        req.setMethod("hub.meter.recharge");
        req.setData(Base64.encode(Json.toJson(data)));

        // 提交请求
        ResponseDTO rsp = doRequest(req);
        return getResponseDataMap(rsp);
    }

    public NutMap openAccount(String meterAddr, String region, String name, String address, Long priceStrategyId) {
        // 请求参数
        NutMap data = NutMap.NEW()
                .setv("meterAddr", meterAddr)
                .setv("region", region)
                .setv("name", name)
                .setv("address", address)
                .setv("priceStrategyId", priceStrategyId);

        RequestDTO req = buildRequestDTO();
        req.setMethod("hub.meter.account.open");
        req.setData(Base64.encode(Json.toJson(data)));

        // 提交请求
        ResponseDTO rsp = doRequest(req);
        return getResponseDataMap(rsp);
    }


    private RequestDTO buildRequestDTO() {
        RequestDTO req = new RequestDTO();
        req.setAppId(appId);
        req.setTimestamp(System.currentTimeMillis() / 1000);
        req.setRequestId(IdUtil.fastSimpleUUID());
        return req;
    }

    private ResponseDTO doRequest(RequestDTO requestDTO) {
        // 签名
        sign(requestDTO);

        Response rsp = Http.post3(baseUrl, Json.toJson(requestDTO),
                Header.create().set("Content-type", "application/json; charset=utf-8"), 3000);
        if (!rsp.isOK()) {
            throw new RuntimeException("Request failed, http code " + rsp.getStatus());
        }

        // 验签
        ResponseDTO rspDTO = Json.fromJson(ResponseDTO.class, rsp.getContent());
        if (!verifySign(rspDTO)) {
            throw new RuntimeException("Response verify sign failed");
        }

        return rspDTO;
    }

    private void sign(RequestDTO req) {
        String text = SignUtil.sortAndJoinField(req); // 按字段名排序并连接
        String signValue = Base64.encode(signCipher.sign(StrUtil.utf8Bytes(text)));
        req.setSign(signValue);
    }

    private boolean verifySign(ResponseDTO rsp) {
        String text = SignUtil.sortAndJoinField(rsp); // 按字段名排序并连接
        return signCipher.verify(StrUtil.utf8Bytes(text), Base64.decode(rsp.getSign()));
    }

    private NutMap getResponseDataMap(ResponseDTO rsp) {
        if (!"200".equalsIgnoreCase(rsp.getCode())) {
            System.err.println("failed : " + rsp.getError());
        }

        String data = rsp.getData();
        if (Strings.isBlank(data)) {
            return NutMap.NEW();
        }

        return Json.fromJson(NutMap.class, Base64.decodeStr(data));
    }
}
